`````
\documentclass{article}

\newcommand{\commonfolder}{../../common-files}

\input{\commonfolder/header}
\input{\commonfolder/copyright}

\lhead{\bfseries SEED Labs -- Environment Variable and \setuid Program Lab}

\begin{document}

\begin{center}
    {\LARGE Environment Variable and \setuid Program Lab}
\end{center}

\seedlabcopyright{2006 - 2016}

% *******************************************
% SECTION
% *******************************************
\section{概述}

本实验的学习目标是让学生了解环境变量如何影响程序和系统的行为。环境变量是一组动态命名的值，可以影响运行在计算机上的进程行为。它们被大多数操作系统使用，并且早在1979年就被引入到Unix中。尽管环境变量会影响程序的行为，但许多程序员并不完全理解它们是如何实现这一点的。因此，如果一个程序使用了环境变量，而程序员又不知道这点，那么这个程序可能会存在安全漏洞。

在本实验中，学生们将了解环境变量的工作原理、如何从父进程传递到子进程以及它们如何影响系统/程序的行为。我们特别关注环境变量如何影响特权程序（\setuid 程序）的行为。本次实验涵盖以下主题：

\begin{itemize}[noitemsep]
    \item 环境变量
    \item \setuid 程序
    \item 安全调用外部程序
    \item 权能泄露
    \item 动态加载器/链接器
\end{itemize}

\paragraph{参考资料。}有关 \setuid 机制、环境变量及其相关安全问题的详细内容，请参阅以下资料：

\begin{itemize}
    \item SEED Book 第1章和第2章，\seedbook
    \item Udemy 上的 SEED 讲座第2节，\seedcsvideo
\end{itemize}

\paragraph{实验环境。} \seedenvironmentC

% *******************************************
% SECTION
% *******************************************
\section{实验任务}

用于本实验所需的文件已包含在 \texttt{Labsetup.zip} 中，并可以从实验网站下载。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务1：操纵环境变量}

在这个任务中，我们将学习如何使用命令设置和取消设置环境变量。我们将在 seed 账户下使用 Bash。用户默认使用的 shell 程序在 \texttt{/etc/passwd} 文件的最后一列中指定（每条记录的最后一个字段）。你可以通过使用 \texttt{chsh} 命令来更改这个程序（请勿为此实验更改此设置）。请完成以下任务：

\begin{itemize}
    \item 使用 \texttt{printenv} 或 \texttt{env} 命令打印环境变量。如果你对某些特定的环境变量感兴趣，比如 \texttt{PWD}，你可以使用 \texttt{"printenv PWD"} 或 \texttt{"env | grep PWD"}。
    \item 使用 \texttt{export} 和 \texttt{unset} 命令来设置或取消设置环境变量。需要注意的是，
      这两个命令并不是独立的程序；它们是 Bash 内置命令（你无法在 Bash 外找到它们）。
\end{itemize}

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务2：从父进程向子进程传递环境变量}

在这个任务中，我们将研究子进程如何从其父进程中获取环境变量。在 Unix 中，
\texttt{fork()} 通过复制调用进程来创建一个新进程。新的进程（称为子进程）是调用进程（称为父进程）的完全副本；然而，某些内容不会被继承（请通过运行 \texttt{man fork} 查看 \texttt{fork()} 的手册以了解详情）。在这个任务中，我们要知道父进程的环境变量是否会被传递给子进程。

\paragraph{步骤1。} 请编译并运行以下程序，并描述你的观察结果。该程序位于 \texttt{Labsetup} 文件夹中；你可以使用 \texttt{"gcc myprintenv.c"} 进行编译，这将生成一个名为 \texttt{a.out} 的二进制文件。让我们运行它并将输出保存到一个文件中，使用命令 \texttt{"a.out > file"}。

\begin{lstlisting}[language=C, caption={\texttt{myprintenv.c}}]
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

extern char **environ;
void printenv()
{
  int i = 0;
  while (environ[i] != NULL) {
     printf("%s\n", environ[i]);
     i++;
  }
}

void main()
{
  pid_t childPid;
  switch(childPid = fork()) {
    case 0:  /* 子进程 */
      printenv();          (*@\ding{192}@*)
      exit(0);
    default:  /* 父进程 */
      //printenv();        (*@\ding{193}@*)
      exit(0);
  }
}
\end{lstlisting}

\paragraph{步骤2。} 现在取消注释子进程中 \texttt{Line \ding{192}} 的 \texttt{printenv()} 语句，并取消注释父进程中的 \texttt{Line \ding{193}} 的 \texttt{printenv()} 语句。重新编译并运行代码，描述你的观察结果。将输出保存到另一个文件中。

\paragraph{步骤3。} 使用 \texttt{diff} 命令比较这两个文件的不同之处，请得出结论。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务3：环境变量和 \texttt{execve()}}

在这个任务中，我们将研究在通过 \texttt{execve()} 执行新程序时环境变量如何受到影响。函数 \texttt{execve()} 调用系统调用来加载并执行新的命令；此函数不会返回。不会创建新进程；相反，调用进程的文本、数据、bss 和堆栈将被加载的新程序覆盖。基本上，\texttt{execve()} 在调用进程中运行新的程序。我们对环境变量会发生什么情况感兴趣：它们是否会自动由新程序继承？

\paragraph{步骤1。} 请编译并运行以下程序，并描述你的观察结果。该程序简单地执行一个名为 \texttt{/usr/bin/env} 的程序，该程序会打印当前进程的环境变量。

\begin{lstlisting}[language=C, caption=\texttt{myenv.c}]
#include <unistd.h>

extern char **environ;
int main()
{
  char *argv[2];

  argv[0] = "/usr/bin/env";
  argv[1] = NULL;
  execve("/usr/bin/env", argv, NULL);    (*@\ding{192}@*)

  return 0 ;
}
\end{lstlisting}

\paragraph{步骤2。} 将 \texttt{Line \ding{192}} 中的 \texttt{execve()} 调用更改为以下内容；描述你的观察结果。

\begin{lstlisting}[language=C]
execve("/usr/bin/env", argv, environ);
\end{lstlisting}

\paragraph{步骤3。} 请根据新程序如何获取环境变量得出结论。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务4：环境变量和 \texttt{system()}}

在这个任务中，我们将研究在通过 \texttt{system()} 函数执行新程序时环境变量会受到什么影响。此函数用于执行命令，但与直接执行命令的 \texttt{execve()} 不同，\texttt{system()} 实际上执行 \texttt{"/bin/sh -c command"}，即它执行 \texttt{/bin/sh}，并要求 shell 执行该命令。

如果你查看 \texttt{system()} 函数的实现，你会看到它使用了 \texttt{execl()} 来执行 \texttt{/bin/sh}；\texttt{execl()} 调用了 \texttt{execve()} 并传递了环境变量数组。因此，使用 \texttt{system()} 时，调用进程的环境变量将被传递给新程序 \texttt{/bin/sh}。请编译并运行以下程序以验证这一点。

\begin{lstlisting}[language=C]
#include <stdio.h>
#include <stdlib.h>

int main()
{
  system("/usr/bin/env");
  return 0 ;
}
\end{lstlisting}

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务5：环境变量和 \setuid 程序}

\setuid 是 Unix 操作系统中的一个重要安全机制。当运行一个 \setuid 程序时，它会假设所有者的所有权。例如，如果程序的所有者是 root，则任何用户在运行此程序时，在执行期间会获得 root 的权限。
\setuid 允许我们做很多有趣的事情，但由于它可以提升用户的权限，因此非常危险。尽管 \setuid 程序的行为由其程序逻辑决定而不受用户影响，但用户确实可以通过环境变量来影响这些行为。为了了解 \setuid 程序是如何受影响的，我们先要弄清楚在用户的进程从用户进程继承到 \setuid 进程中的环境变量。

\paragraph{步骤1。} 编写以下程序以打印当前进程中的所有环境变量。

\begin{lstlisting}[language=C]
#include <stdio.h>
#include <stdlib.h>

extern char **environ;
int main()
{
  int i = 0;
  while (environ[i] != NULL) {
    printf("%s\n", environ[i]);
    i++;
  }
}
\end{lstlisting}

\paragraph{步骤2。} 编译上述程序，将其所有权更改为 root，并使其成为一个 \setuid 程序。

\begin{lstlisting}
// 假设程序名为 foo
$ sudo chown root foo
$ sudo chmod 4755 foo
\end{lstlisting}

\paragraph{步骤3。} 在你的 shell 中（你需要在普通用户账号下，而不是 root 账号），使用 \texttt{export} 命令设置以下环境变量（它们可能已经存在）：

\begin{itemize}[noitemsep]
    \item \texttt{PATH}
    \item \texttt{LD\_LIBRARY\_PATH}
    \item \texttt{ANY\_NAME}（这是你定义的环境变量，所以你可以选择任意名称）
\end{itemize}

这些环境变量在用户的 shell 进程中设置。现在，在你的 shell 中运行步骤2中的 \setuid 程序。当你在 shell 中输入程序名称时，shell 会 fork 一个子进程，并使用该子进程来运行程序。请检查你是否将 shell 进程（父进程）设置的所有环境变量传递到了 \setuid 子进程中。描述你的观察结果；如果有让你惊讶的情况，请详细描述。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务6：PATH 环境变量和 \setuid 程序}

由于 shell 程序的调用，在 \setuid 程序中使用 \texttt{system()} 可能非常危险。这是因为实际由 shell 执行的行为可能会受到环境变量（例如 \texttt{PATH}）的影响；这些环境变量由用户提供，可能具有恶意意图。通过更改这些变量，恶意用户可以控制 \setuid 程序的行为。在 Bash 中，你可以以以下方式更改 \texttt{PATH} 环境变量（此示例将在 \texttt{PATH} 变量中将目录 \texttt{/home/seed} 添加到前面）：

\begin{lstlisting}
$ export PATH=/home/seed:$PATH
\end{lstlisting}

下面的 \setuid 程序本应执行 \texttt{/bin/ls} 命令，但程序员仅使用相对路径来调用 \texttt{ls} 命令，而不是绝对路径：

\begin{lstlisting}[language=C]
int main()
{
  system("ls");
  return 0;
}
\end{lstlisting}

请编译上述程序，更改其所有者为 root，并将其设置为一个 \setuid 程序。你能使用此 \setuid 程序来运行你自己的恶意代码而不是 \texttt{/bin/ls} 吗？如果可以，请问你的恶意代码是否以 root 权限执行了？请描述并解释你的观察结果。

\paragraph{注意：}
\texttt{system(cmd)} 函数会首先执行 \texttt{/bin/sh}，然后要求这个 shell 程序来运行 \texttt{cmd} 命令。
在 Ubuntu 20.04（以及若干版本之前），\texttt{/bin/sh} 实际上是一个指向 \texttt{/bin/dash} 的符号链接。此 shell 程序有一个对策可以防止它在 \setuid 进程中被执行。基本上，如果 \texttt{dash} 检测到它在一个 \setuid 进程中被执行，则会立即将其有效用户 ID 更改为进程的实际用户 ID，从而放弃权限。

由于我们的目标程序是一个 \setuid 程序，
\texttt{/bin/dash} 中的这个对策可以阻止我们发起攻击。为了看到没有这种对策时我们的攻击如何运作，请将 \texttt{/bin/sh} 链接到另一个没有这种对策的 shell 程序中。
我们在 Ubuntu 20.04 虚拟机上安装了一个叫做 \texttt{zsh} 的 shell 程序。我们使用以下命令来链接 \texttt{/bin/sh} 到 \texttt{/bin/zsh}：

\begin{lstlisting}
$ sudo ln -sf /bin/zsh /bin/sh
\end{lstlisting}

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务7：LD\_PRELOAD 环境变量和 \setuid 程序}

在本任务中，我们将研究 \setuid 程序如何处理某些环境变量。包括 \texttt{LD\_PRELOAD}、\texttt{LD\_LIBRARY\_PATH} 及其他 \texttt{LD\_*} 在内的几个环境变量会影响动态加载器/链接器的行为。
动态加载器/链接器是操作系统（OS）的一部分，它在运行时从持久存储中加载并连接所需的共享库。

在 Linux 中，\texttt{ld.so} 或 \texttt{ld-linux.so} 是动态加载器/链接器（每个适用于不同类型的二进制文件）。其中影响其行为的环境变量中，我们在这次实验中关心的是 \texttt{LD\_LIBRARY\_PATH} 和 \texttt{LD\_PRELOAD}。在 Linux 中，\texttt{LD\_LIBRARY\_PATH} 是一个由冒号分隔的目录集，在标准搜索目录之前首先被用来查找库。
\texttt{LD\_PRELOAD} 指定要在所有其他共享库之前加载的一个或多个用户指定的共享库。在本任务中，我们仅研究 \texttt{LD\_PRELOAD}。

\paragraph{步骤1。}
首先，我们来看这些环境变量如何影响运行正常程序时动态加载器/链接器的行为。
请按照以下步骤操作：

\begin{enumerate}
    \item 我们将构建一个动态链接库。创建以下程序，并将其命名为 \texttt{mylib.c}，它基本上是重写了 \texttt{libc} 中的 \texttt{sleep()} 函数：
        \begin{lstlisting}[language=C]
#include <stdio.h>
void sleep (int s)
{
  /* 如果此函数被权限较高的程序调用，
     你可以在其中做点坏事！ */
  printf("I am not sleeping!\n");
}
        \end{lstlisting}

    \item 我们可以使用以下命令编译上述程序（在 \texttt{-lc} 参数中，第二个字符是 $\ell$）：
        \begin{lstlisting}
$ gcc -fPIC -g -c mylib.c
$ gcc -shared -o libmylib.so.1.0.1 mylib.o -lc
        \end{lstlisting}

    \item 现在设置 \texttt{LD\_PRELOAD} 环境变量：
        \begin{lstlisting}
$ export LD_PRELOAD=./libmylib.so.1.0.1
        \end{lstlisting}

    \item 最后，编译名为 \texttt{myprog} 的以下程序，并将其放在与上述动态链接库 \texttt{libmylib.so.1.0.1} 同一目录下：
        \begin{lstlisting}[language=C]
/* myprog.c */
#include <unistd.h>
int main()
{
  sleep(1);
  return 0;
}
        \end{lstlisting}
\end{enumerate}

\paragraph{步骤2。}在完成上述操作后，请分别以以下条件运行 \texttt{myprog} 并观察发生了什么。

    \begin{itemize}
        \item 将 \texttt{myprog} 设置为一个普通的程序，并以普通用户身份运行它。
        \item 将 \texttt{myprog} 作为根权限的程序，再以普通用户身份运行它。
        \item 再次在 root 账号中导出 \texttt{LD\_PRELOAD} 环境变量并运行此程序。
        \item 将 \texttt{myprog} 设置为一个由 user1 用户所有（即另一个用户账号）的 \setuid 程序，并在其他用户的账户中再次导出 \texttt{LD\_PRELOAD} 环境变量，然后运行它。
    \end{itemize}

\paragraph{步骤3。}你应能观察到不同场景下的行为差异，即使你在运行相同程序。你需要找出导致这些差异的原因。环境变量在其中发挥了作用。请设计一个实验以找出主要原因，并解释步骤2中的行为有何不同。（提示：子进程可能不会继承 \texttt{LD\_*} 环境变量）。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务8：使用 {\tt system()} 和 {\tt execve()} 调用外部程序}

虽然 {\tt system()} 和 {\tt execve()} 都可以用来运行新程序，但若在特权程序（如 \setuid 程序）中使用 {\tt system()} 就非常危险。我们已经看到环境变量如何影响 {\tt system()} 的行为，因为此变量会影响 shell 的工作方式。{\tt execve()} 不会有这个问题，因为它不会调用 shell。调用 shell 本身就有另一个危险的后果，并且这次与环境变量无关。让我们来看一下以下场景。

Bob 是一个审计机构的工作人员，他需要调查一家公司是否涉嫌欺诈。为了进行调查，Bob 需要能够查看该公司的 Unix 系统中的所有文件；另一方面，为了保护系统的完整性，Bob 不应该修改任何文件。
为实现这个目标，系统超级用户 Vince 写了一个特别的 set-root-uid 程序（见下文），然后将可执行权限给了 Bob。此程序要求 Bob 在命令行中输入一个文件名，然后它会运行 {\tt /bin/cat} 以显示指定的文件。由于该程序是作为 root 运行的，所以它可以显示 Bob 指定的任何文件。然而，由于该程序没有任何写操作，Vince 非常确信 Bob 不可能使用此特殊程序来修改任何文件。

\begin{lstlisting}[language=C, caption=\texttt{catall.c}]
int main(int argc, char *argv[])
{
  char *v[3];
  char *command;

  if(argc < 2) {
    printf("Please type a file name.\n");
    return 1;
  }

  v[0] = "/bin/cat"; v[1] = argv[1]; v[2] = NULL;
  command = malloc(strlen(v[0]) + strlen(v[1]) + 2);
  sprintf(command, "%s %s", v[0], v[1]);

  // 只使用以下之一。
  system(command);
  // execve(v[0], v, NULL);

  return 0 ;
}
\end{lstlisting}

\paragraph{步骤1：} 编译上述程序，使其成为由 root 所拥有的 \setuid 程序。该程序将使用 {\tt system()} 来调用命令。
如果以 Bob 的身份运行，你能损害系统的完整性吗？例如，你能否删除一个不可写给你的文件？

\paragraph{步骤2：} 取消注释 {\tt system(command)} 语句，并取消注释 {\tt execve()} 语句；程序将使用 {\tt execve()} 来调用命令。重新编译该程序并使其成为 root 所拥有的 \setuid 程序。
步骤1中的攻击是否仍然有效？请描述并解释你的观察结果。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务9：权能泄露}

遵循最小权限原则，\setuid 程序通常会在不再需要时永久放弃其 root 权限。此外，有时程序需要将其控制权交给用户；在这种情况下，必须撤销这些权限。
可以使用 {\tt setuid()} 系统调用来撤销这些权限。
根据手册，“{\tt setuid()} 设置了调用进程的有效用户 ID。如果调用者的有效 UID 是 root，则实际的 UID 和已保存的设置用户-ID 也会被设置”。因此，如果一个拥有有效 UID 0 的 \setuid 程序调用了 {\tt setuid(n)}，此过程将变成一个普通的过程，并且所有其 UIDs 将被设置为 \texttt{n}。

撤销权限时的一个常见错误是权能泄露。进程可能在仍具有特权时获得了某些特殊权限；当权限被降级时，如果程序不清理这些权限，则它们仍然可以被非特权进程访问。
换句话说，虽然该过程的有效用户 ID 变为非特权的，但该过程仍然是特权的，因为它拥有特权权限。

编译以下程序并将其所有者更改为 root。然后使其成为 \setuid 程序。以普通用户身份运行此程序。能否利用此程序中的权能泄露漏洞？目标是作为普通用户写入 {\tt /etc/zzz} 文件。

\begin{lstlisting}[language=C, caption=\texttt{cap\_leak.c}]
void main()
{
  int fd;
  char *v[2];

  /* 假设 \texttt{/etc/zzz} 是一个重要的系统文件，
   * 并且它的所有者是 root，权限为 0644。
   * 在运行此程序之前，请先创建文件 \texttt{/etc/zzz}。*/
  fd = open("/etc/zzz", O_RDWR | O_APPEND);
  if (fd == -1) {
     printf("Cannot open /etc/zzz\n");
     exit(0);
  }

  // 打印出文件描述符值
  printf("fd is %d\n", fd);

  // 永久性地放弃权限，使有效用户 ID 与实际用户 ID 相同
  setuid(getuid());

  // 运行 \texttt{/bin/sh}
  v[0] = "/bin/sh"; v[1] = NULL;
  execve(v[0], v, NULL);
}
\end{lstlisting}

% *******************************************
% SECTION
% *******************************************
\section{提交}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\commonfolder/submission}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\end{document}
```